package org.hilo.boot.core.shiro;

import java.util.Collection;
import java.util.LinkedHashMap;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.jretty.apibase.Result;
import org.hilo.boot.core.UT;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 用于ajax登录。建议保持 和 AuthenticatingFilter（FormAuthenticationFilter） 一致的逻辑。
 * @see AuthenticatingFilter
 * 
 * @author zollty
 * @since 2019年4月29日
 */
public class AjaxLoginHelper {
    
    private static final Logger logger = LoggerFactory.getLogger(AjaxLoginHelper.class);
    
    public static Result<String> doLogin(AuthenticationToken token) throws AuthenticationException {
        Subject subject = SecurityUtils.getSubject();
        // Stop session fixation issues.
        // https://issues.apache.org/jira/browse/SHIRO-170

        Session session = subject.getSession();
        String old_id = (String) session.getId();
        // Store the attributes so we can copy them to the new session after auth.
        final LinkedHashMap<Object, Object> attributes = new LinkedHashMap<Object, Object>();
        final Collection<Object> keys = session.getAttributeKeys();
        for (Object key : keys) {
            final Object value = session.getAttribute(key);
            if (value != null) {
                attributes.put(key, value);
            }
        }
        session.stop();

        subject.login(token);
        
        // Restore the attributes.
        session = subject.getSession();
        logger.debug("OWASP session fixation from " + old_id + " to " + session.getId());
        for (final Object key : attributes.keySet()) {
            session.setAttribute(key, attributes.get(key));
        }
        
        return Result.success(String.valueOf(session.getId()));
    }


    public static UsernamePasswordToken createToken(HttpServletRequest request, HttpServletResponse response) {
        String username = getUsername(request);
        String password = getPassword(request);
        boolean rememberMe = isRememberMe(request);
        String host = getHost(request);
        return createToken(username, password, rememberMe, host);
    }

    public static UsernamePasswordToken createToken(String username, String password,
                                              ServletRequest request, ServletResponse response) {
        boolean rememberMe = isRememberMe(request);
        String host = getHost(request);
        return createToken(username, password, rememberMe, host);
    }

    public static UsernamePasswordToken createToken(String username, String password,
                                              boolean rememberMe, String host) {
        return new UsernamePasswordToken(username, password, rememberMe, host);
    }

    /**
     * Returns the host name or IP associated with the current subject.  This method is primarily provided for use
     * during construction of an <code>AuthenticationToken</code>.
     * <p/>
     * The default implementation merely returns {@link ServletRequest#getRemoteHost()}.
     *
     * @param request the incoming ServletRequest
     * @return the <code>InetAddress</code> to associate with the login attempt.
     */
    public static String getHost(ServletRequest request) {
        return UT.Web.getIpAddr((HttpServletRequest) request);
    }

    public static String getUsername(ServletRequest request) {
        return WebUtils.getCleanParam(request, FormAuthenticationFilter.DEFAULT_USERNAME_PARAM);
    }

    public static String getPassword(ServletRequest request) {
        return WebUtils.getCleanParam(request, FormAuthenticationFilter.DEFAULT_PASSWORD_PARAM);
    }
    
    /**
     * Returns <code>true</code> if &quot;rememberMe&quot; should be enabled for the login attempt associated with the
     * current <code>request</code>, <code>false</code> otherwise.
     * <p/>
     * This implementation always returns <code>false</code> and is provided as a template hook to subclasses that
     * support <code>rememberMe</code> logins and wish to determine <code>rememberMe</code> in a custom mannner
     * based on the current <code>request</code>.
     *
     * @param request the incoming ServletRequest
     * @return <code>true</code> if &quot;rememberMe&quot; should be enabled for the login attempt associated with the
     *         current <code>request</code>, <code>false</code> otherwise.
     */
    public static boolean isRememberMe(ServletRequest request) {
        return WebUtils.isTrue(request, FormAuthenticationFilter.DEFAULT_REMEMBER_ME_PARAM);
    }

    
    /**
     * 根据header判断，不是特别准确，遇到特殊情况再说吧。
     */
    public static boolean isAjaxRequest(HttpServletRequest request) {
        String httpMethod = request.getMethod().toUpperCase();
        // 默认“非GET请求都是AJAX请求，非AJAX请求都是GET请求”
        if (!httpMethod.equals("GET")) {
            return true;
        }
        
        String header = request.getHeader(HeaderBasedWebSessionManager.HEADER_SESSION_ID_NAME);
        if (header != null) {
            return true;
        }
        String secFetchMode = request.getHeader("sec-fetch-mode");
        if (secFetchMode != null && secFetchMode.startsWith("cors")) {
            return true;
        }
        String accept = request.getHeader("accept");
        if (accept != null && accept.startsWith("application/json")) {
            return true;
        }
        return UT.Web.isAjaxRequest((HttpServletRequest) request);
    }
}
